/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "interruptmanager.h"

#include <kerneldebugconsole.h>
#include <machine/pc/processor/processor.h>
#include <machine/machine.h>
#include <lib/lockguard.h>
#include <bootoutput.h>

const char* g_ExceptionNames[] = {
	"Divide Error",
	"Debug",
	"NMI Interrupt",
	"Breakpoint",
	"Overflow",
	"BOUND Range Exceeded",
	"Invalid Opcode",
	"Device Not Available",
	"Double Fault",
	"Coprocessor Segment Overrun",
	"Invalid TSS",
	"Segment Not Present",
	"Stack Fault",
	"General Protection Fault",
	"Page Fault",
	"Reserved: Interrupt 15",
	"FPU Floating-Point Error",
	"Alignment Check",
	"Machine-Check",
	"SIMD Floating-Point Exception",
	"Reserved: Interrupt 20",
	"Reserved: Interrupt 21",
	"Reserved: Interrupt 22",
	"Reserved: Interrupt 23",
	"Reserved: Interrupt 24",
	"Reserved: Interrupt 25",
	"Reserved: Interrupt 26",
	"Reserved: Interrupt 27",
	"Reserved: Interrupt 28",
	"Reserved: Interrupt 29",
	"Reserved: Interrupt 30",
	"Reserved: Interrupt 31"
};

bool PLInterruptManager::registerInterruptHandler(size_t nInterruptNumber, InterruptHandler *pHandler) {
	LockGuard<Spinlock> lockGuard(m_Lock);
	if(nInterruptNumber >= 255) {
		return false;
	}
	if(m_pHandler[nInterruptNumber] != 0) {
		return false;
	}
	if(pHandler == 0) {
		return false;
	}
	m_pHandler[nInterruptNumber] = pHandler;
	return true;
}

bool PLInterruptManager::registerInterruptHandlerDebugger(size_t nInterruptNumber, InterruptHandler *pHandler) {
	LockGuard<Spinlock> lockGuard(m_Lock);
	if(nInterruptNumber >= 255) {
		return false;
	}
	if(m_pDbgHandler[nInterruptNumber] != 0) {
		return false;
	}
	if(pHandler == 0) {
		return false;
	}
	m_pDbgHandler[nInterruptNumber] = pHandler;
	return true;
}

void PLInterruptManager::interrupt(InterruptState &interruptState) {
	size_t nIntNumber = interruptState.getInterruptNumber();
	InterruptHandler *pHandler;
	// Get the debugger handler
	pHandler = PLProcessor::instance().interruptManager().m_pDbgHandler[nIntNumber];
	// Call the kernel debugger's handler, if any
	if(pHandler != 0) {
		pHandler->interrupt(nIntNumber, interruptState);
	}
	// Get the interrupt handler
	pHandler = PLProcessor::instance().interruptManager().m_pHandler[nIntNumber];
	// Call the normal interrupt handler, if any
	if(pHandler != 0) {
		LockGuard<Spinlock> lockGuard(PLProcessor::instance().interruptManager().m_Lock);
		if(!pHandler->interrupt(nIntNumber, interruptState)) {
			PLProcessor::instance().interruptManager().interruptOutput(interruptState);
		}
		return;
	}
	PLProcessor::instance().interruptManager().interruptOutput(interruptState);
}

void PLInterruptManager::interruptOutput(InterruptState &interruptState) {
	if(!PLMachine::instance().kernelCommandline()->noInterruptDetails()) {
		PLProcessor::instance().interruptManager().interruptOutputPrint(interruptState);
	} else {
		size_t nIntNumber = interruptState.getInterruptNumber();
		PLBootOutput::instance().printf("\nInterrupt %s (%d)", g_ExceptionNames[nIntNumber], nIntNumber);
		PLBootOutput::instance().printf(" ERROR: 0x%x", interruptState.m_Errorcode);
		PLProcessor::instance().halt();
	}
}

void PLInterruptManager::interruptOutputPrint(InterruptState &interruptState) {
	size_t nIntNumber = interruptState.getInterruptNumber();
	if(nIntNumber < 32) {
		PLKernelDebugConsole::instance().setDebugActive(false);
		PLBootOutput::instance().cls();
		for(int i = 0 ; i < PLBootOutput::instance().width() ; i++) {
			PLBootOutput::instance().putchar(177, PLColorVGA16(PLColorVGA16::red, PLColorVGA16::black));
		}
		PLBootOutput::instance().printf("Interrupt: %s (%d)\n", g_ExceptionNames[nIntNumber], nIntNumber);
		for(int i = 0 ; i < PLBootOutput::instance().width() ; i++) {
			PLBootOutput::instance().putchar(177, PLColorVGA16(PLColorVGA16::red, PLColorVGA16::black));
		}
		PLBootOutput::instance().printf("\n");
		PLBootOutput::instance().printf("Stack Pointer: 0x%x", interruptState.getStackPointer());
		PLBootOutput::instance().moveX(30);
		PLBootOutput::instance().printf("Instroduction Pointer: 0x%x\n", interruptState.getInstructionPointer());
		PLBootOutput::instance().printf("Base Pointer:  0x%x", interruptState.getBasePointer());
		PLBootOutput::instance().moveX(30);
		PLBootOutput::instance().printf("Mode:                  %s mode\n\n", interruptState.kernelMode() ? "Kernel" : "User");
		PLBootOutput::instance().printf("Register Information:\n");
		PLBootOutput::instance().printf("=====================");
		for(size_t i = 0 ; i < interruptState.getRegisterCount() ; i++) {
			if(i % 2 == 0) {
				PLBootOutput::instance().printf("\n");
			}
			if(i % 2 == 1) {
				PLBootOutput::instance().moveX(40);
			}
			PLBootOutput::instance().printf("%s:", interruptState.getRegisterName(i));
			if(i % 2 == 0) {
				PLBootOutput::instance().moveX(8);
			}
			if(i % 2 == 1) {
				PLBootOutput::instance().moveX(48);
			}
			PLBootOutput::instance().printf("0x%x", interruptState.getRegister(i));
			if(i % 2 == 0) {
				PLBootOutput::instance().moveX(23);
			}
			if(i % 2 == 1) {
				PLBootOutput::instance().moveX(63);
			}
			PLBootOutput::instance().printf("(%d)", interruptState.getRegister(i));
		}
		uint32_t cr0 = PLProcessor::instance().controlRegister0Value();
		uint32_t cr2 = PLProcessor::instance().controlRegister2Value();
		uint32_t cr3 = PLProcessor::instance().controlRegister3Value();
		PLBootOutput::instance().printf("\n");
		PLBootOutput::instance().printf("CR0:");
		PLBootOutput::instance().moveX(8);
		PLBootOutput::instance().printf("0x%x", cr0);
		PLBootOutput::instance().moveX(23);
		PLBootOutput::instance().printf("(%d)", cr0);
		PLBootOutput::instance().moveX(40);
		PLBootOutput::instance().printf("CR2:");
		PLBootOutput::instance().moveX(48);
		PLBootOutput::instance().printf("0x%x", cr2);
		PLBootOutput::instance().moveX(63);
		PLBootOutput::instance().printf("(%d)\n", cr2);
		PLBootOutput::instance().printf("CR3:");
		PLBootOutput::instance().moveX(8);
		PLBootOutput::instance().printf("0x%x", cr3);
		PLBootOutput::instance().moveX(23);
		PLBootOutput::instance().printf("(%d)", cr3);
		PLBootOutput::instance().moveX(40);
		PLBootOutput::instance().printf("ERROR:");
		PLBootOutput::instance().moveX(48);
		PLBootOutput::instance().printf("0x%x", interruptState.m_Errorcode);
		PLBootOutput::instance().moveX(63);
		PLBootOutput::instance().printf("(%d)", interruptState.m_Errorcode);
		PLBootOutput::instance().printf("\n\n");
		if(nIntNumber == 10 || nIntNumber == 11 || nIntNumber == 12 || nIntNumber == 13 || nIntNumber == 14) {
			PLBootOutput::instance().printf("Error description of the error flags:\n");
			PLBootOutput::instance().printf("=====================================\n");
		}
		if(nIntNumber == 10) {
			PLBootOutput::instance().printf("The Invalid TSS exception was from selector index %d.", interruptState.m_Errorcode);
		} else if(nIntNumber == 11) {
			PLBootOutput::instance().printf("The segment selector index of the segment descriptor which caused the exception was %d.", interruptState.m_Errorcode);
		} else if(nIntNumber == 12) {
			PLBootOutput::instance().printf("The stack segment selector index was %d.", interruptState.m_Errorcode);
		} else if(nIntNumber == 13) {
			PLBootOutput::instance().printf("The segment selector index was %d.", interruptState.m_Errorcode);
		} else if(nIntNumber == 14) {
			int errorNumber = 0;
			PageFaultHandler::PageFaultError err = PageFaultHandler::PageFaultError(interruptState.m_Errorcode);
			if(err.testFlag(PageFaultHandler::ProtectionViolation)) {
				PLBootOutput::instance().printf("The fault was caused by a protection violation.");
				setNewError(&errorNumber);
			}
			if(err.testFlag(PageFaultHandler::WriteAccessError)) {
				PLBootOutput::instance().printf("Write access caused the fault.");
				setNewError(&errorNumber);
			}
			if(err.testFlag(PageFaultHandler::UserMode)) {
				PLBootOutput::instance().printf("The fault occurred in user mode.");
				setNewError(&errorNumber);
			}
			if(err.testFlag(PageFaultHandler::ReservedBits)) {
				PLBootOutput::instance().printf("One or more page directory entries contain reserved bits which are set to 1.");
				setNewError(&errorNumber);
			}
			if(err.testFlag(PageFaultHandler::InstructionFetch)) {
				PLBootOutput::instance().printf("The fault was caused by an instruction fetch.");
				setNewError(&errorNumber);
			}
		}
		PLProcessor::instance().halt();
	}
}

void PLInterruptManager::setNewError(int *errorNumber) {
	(*errorNumber)++;
	if((*errorNumber % 2) == 0) {
		PLBootOutput::instance().printf("\n");
	} else {
		PLBootOutput::instance().moveX(40);
	}
}

void PLInterruptManager::initialiseProcessor() {
	registerInterruptHandler(14, &m_pageFaultHandler);
	struct {
		uint16_t size;
		uint32_t idt;
	} PACKED idtr = {2047, reinterpret_cast<uintptr_t>(&PLProcessor::instance().interruptManager().m_IDT)};
	asm volatile("lidt %0" :: "m"(idtr));
}

void PLInterruptManager::setInterruptGate(size_t nInterruptNumber, uintptr_t interruptHandler, bool bUserspace) {
	m_IDT[nInterruptNumber].offset0 = interruptHandler & 0xFFFF;
	m_IDT[nInterruptNumber].selector = 0x08;
	m_IDT[nInterruptNumber].res = 0;
	m_IDT[nInterruptNumber].flags = bUserspace ? 0xEE : 0x8E;
	m_IDT[nInterruptNumber].offset1 = (interruptHandler >> 16) & 0xFFFF;
}

void PLInterruptManager::setTaskGate(size_t nInterruptNumber, uint16_t tssSeg) {
	m_IDT[nInterruptNumber].offset0 = 0;
	m_IDT[nInterruptNumber].selector = tssSeg;
	m_IDT[nInterruptNumber].res = 0;
	m_IDT[nInterruptNumber].flags = 0xE5;
	m_IDT[nInterruptNumber].offset1 = 0;
}

PLInterruptManager::PLInterruptManager() : m_Lock(false, true) {
	// Initialise the pointers to the interrupt handler
	for (size_t i = 0 ; i < 256 ; i++) {
		m_pHandler[i] = 0;
		m_pDbgHandler[i] = 0;
	}
	// Initialise the IDT
	extern uintptr_t interrupt_handler_array[];
	for (size_t i = 0 ; i < 256 ; i++) {
		setInterruptGate(i, interrupt_handler_array[i], (i == 3 /* Interrupt number */) ? true : false);
	}
	// Overwrite the double fault handler
	setTaskGate(8, 0x30);
}
